#####################################################################
# File:         Section.py
# Product:      KnowledgeEngine
#
# Copyright:    Copyright (c) 2001, Faulkner Ltd.
# Author:       Brian Oliver
#
# Description:  This file defines the Section class, the 
#               simplest component container.
#
# See Also:     ComponentContainer, Model
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.KnowledgeEngineCore import *
from Products.KnowledgeEngine.ComponentContainer import ComponentContainer, ComponentContainerInfo
from Products.KnowledgeEngine.Model.Model import Model
from Products.KnowledgeEngine.Library.Library import Library
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.KERFProcessor import *

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from Globals import Persistent
from ImageFile import ImageFile

#####################################################################
## Python Library Imports
# (none)

#####################################################################
## SectionInfo Class Definition
class SectionInfo(ComponentContainerInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """

    #---------------------------------------------------------------#
    
    def getClassName(self):
        """
            Return the class name
        """
        
        return 'Section'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """
    
        return "Section"
        
    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.  
        """
        
        return "sec"
        
    #---------------------------------------------------------------#
    
    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """
        
        return ImageFile('www/icon.gif', globals())

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a 
            child of the class' instances.
            
            NOTE: This is from this class' instance perspective.
            
            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """
        
        #DEFAULT RULE: Sections may have Component or Element children
        return childClassInfo.isInstanceOf('Component') or childClassInfo.isInstanceOf('Element') or childClassInfo.isInstanceOf('Event')
    
    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific properties using the PropertyReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """

        #initialise the super-class properties for reflection
        ComponentContainerInfo.onConfigureProperties(self)

        #add the 'navigationTitle' property (usually displayed in the dropdown when the section is at the top-level)
        self.addPropertyDefinition(PropertyDefinition(identity="navigationTitle", displayName="Navigation Title", defaultValue=self.getDisplayName(), description="The text used to navigate to this component (like the dropdown lists in toolbars)", propertyFormat=pf_TEXT_SINGLELINE))
        
        #add the 'isIndented' property        
        self.addPropertyDefinition(PropertyDefinition(identity="isIndented", displayName="Indent Contained Components?", description="Should sub-components of the Section be indented?", defaultValue=true, propertyType=pt_BOOLEAN, propertyFormat=pf_SELECTION + pf_YES_NO, propertyStructure=ps_ATOMIC))        
    
        #add the 'isScored' property        
        self.addPropertyDefinition(PropertyDefinition(identity="isScored", displayName="Show Section Score in Report?", description="Should Section score be added to Scored Reports?", defaultValue=false, propertyType=pt_BOOLEAN, propertyFormat=pf_SELECTION + pf_YES_NO, propertyStructure=ps_ATOMIC))        
    
        #add the 'isPromotingChildren' property        
        self.addPropertyDefinition(PropertyDefinition(identity="isPromotingChildren", displayName="Promote Contained Components?", description="By promoting the sub-components, they may become 'pages' when performing an assessment?", defaultValue=false, propertyType=pt_BOOLEAN, propertyFormat=pf_SELECTION + pf_YES_NO, propertyStructure=ps_ATOMIC))        

    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register 
            class specific events using the EventReflection interface.
            
            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered
            
            NOTE: This method is called before ClassInfo.onAfterStartup()            
        """
                
        #initialise the super-class events for reflection
        ComponentContainerInfo.onConfigureEvents(self)

        #add the onBeforeSaveAssessment event (which occurs prior to an assessment being saved)
        self.addEventDefinition(EventDefinition(identity="onBeforeSaveAssessment", displayName="Before Saving an Assessment", description="Occurs before an assessment is saved", eventType=et_SERVER))

        #add the onBeforeVisiting event (which occurs prior to a component being visited)
        self.addEventDefinition(EventDefinition(identity="onBeforeVisiting", displayName="Before Visiting", description="Occurs before a component is visited", eventType=et_SERVER))

        #add the onBeforeLeaving event (which occurs prior to an assessment being closed)
        self.addEventDefinition(EventDefinition(identity="onBeforeLeaving", displayName="Before Leaving", description="Occurs before a component is left", eventType=et_SERVER))

    #---------------------------------------------------------------#

#####################################################################
## Section Class Definition
class Section(ComponentContainer):
    """
        Simplest Component Container
    """

    #---------------------------------------------------------------#

    #TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = SectionInfo()
 
    #---------------------------------------------------------------#
    # DTML Methods
    index_html = DTMLFile('dtml/index_html', globals())
    simplereport_html = DTMLFile('dtml/simplereport_html', globals())
    scoredreport_html = DTMLFile('dtml/scoredreport_html', globals())
    scored360report_html = DTMLFile('dtml/scored360report_html', globals())
    groupedreport_html = DTMLFile('dtml/groupedreport_html', globals())    
    #csv360report_data = DTMLFile('dtml/csv360report_data', globals())
    
    #---------------------------------------------------------------#
    # Default Attribute Values
    isIndented = 1
    isScored = 0
   
    #---------------------------------------------------------------#

    def __init__(self, id, identity):
        "Constructor for the Section"        

        ComponentContainer.__init__(self, id, identity)

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object 
            has been loaded into memory.  
            
            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation processing
        ComponentContainer.onAfterInstanciation(self)

        #ensure the navigation property is set to the title (for backwards compatibility)
        if not hasattr(self, "navigationTitle"):
            if hasattr(self, "title"):
                self.navigationTitle = self.getTitleOrId()
            else:
                self.navigationTitle = ""

        #add isScored attribute if it doesn't already exist
        if not hasattr(self, "isScored"):
            self.isScored = false
            
        #add isPromotingChildren attribute if it doesn't already exist
        if not hasattr(self, "isPromotingChildren"):
            self.isPromotingChildren = false
            
    #---------------------------------------------------------------#

    def getIsIndented(self):
        "Returns if the section is display child components indented"        
        return self.isIndented

    #---------------------------------------------------------------#

    def setIsIndented(self, isIndented):
        "Set the that the section should be indented"        
        self.isIndented = isIndented

    #---------------------------------------------------------------#

    def getIsScored(self):
        "Returns if the section should be scored"        
        return self.isScored

    #---------------------------------------------------------------#

    def setIsScored(self, isScored):
        "Set the that the section should be scored"        
        self.isScored = isScored

    #---------------------------------------------------------------#

    def getIsPromotingChildren(self):
        "Returns if the section should promote it's children"        
        return self.isPromotingChildren

    #---------------------------------------------------------------#

    def setIsPromotingChildren(self, isPromotingChildren):
        "Set the that the section promote it's children"        
        self.isPromotingChildren = isPromotingChildren
        
    #---------------------------------------------------------------#

    def getPromotedChildren(self, assessment = None):
        """
            Return a sequence of promoted children for this Section
            (returns [] if the section isn't promoting children)
            
            This method is used to determine top-level pages.
            
            If an assessment is not provided, all possible 
            promoted children must be returned.
        """
        
        #for normal sections, we just return the children (if we are promoting)
        if self.getIsPromotingChildren():
            return self.getChildren()
        else:
            return []

                                    
    #---------------------------------------------------------------#

    def getDumpData(self, componentid, assessments, data):
        """
            Returns score and answer content for a componentid given assessmentid
            Used in csvDumps
        """
        
        if data == 'Scores':

            row=[]
            c = componentid

            row.append(c.getIdentity())
            row.append(c.getId())
            row.append('"' + c.getContent() + '"')
            if data == "Scores":
                row.append(c.getWeighting())
            else:
                row.append('NonScored')
            for a in assessments:
                if data == "Scores":
                    row.append(a.getScore(c.getIdentity()))
                else:
                    row.append('NonScored')        

            return row
            
        else:

            row=[]
            c = componentid

            row.append(c.getIdentity())
            row.append(c.getId())
            row.append('"' + c.getContent() + '"')
            if data == "All":
                if c.isPublishable() and c.isInstanceOf("Scorable"):
                    if c.showScore:
                        row.append(c.getWeighting())
                    else:
                        row.append('')
                else:
                    row.append('')
                    
            for a in assessments:
                #### determine what should go in the "Report Content" Column

                # first handle the most complicated case, complicated because a selection can have multiple children (options)
                # and one, none or many options can be selected
                if c.getIdentityType()=='sel':
                    content = []
                    index = 0
                    rc = ''
                    for d in c.getChildren():

                        # the following conditional counts on Excel's ability to qualify textual fields
                        # by quoting - this allows us to handle the commas inherent
                        # in the content of multi-option selection components, such that text is imported into
                        # one cell as opposed to being forcibly comma delimited

                        if a.getState(d.getIdentity()) and d.getReportContentWhenSelected():
                            rc = resolveKERF(d.getReportContentWhenSelected(), a, d)
                            # strip out line feeds from possibly multi-line text field content
                            if index == 0:
                                content.append('"' + rc)
                            else:
                                content.append(rc)
                            index = index + 1     
                        elif not a.getState(d.getIdentity()) and d.getReportContentWhenUnselected():
                            rc = resolveKERF(d.getReportContentWhenUnselected(), a, d)
                            if index == 0:
                                content.append('"' + rc)
                            else:
                                content.append(rc)
                            index += 1
                    if len(content) != 0: #i.e. a selection was made - trim off the last comma and close the quotations
                        length = len(content)-1
                        content[length] = content[length] + '"'
                    else: # still need to return something to keep columns aligned properly
                        content = ['']
                else: 
                    if c.getIdentityType() == 'mch':
                        #content = 'mch content'
                        content = c.getAnswerReportContent(a)
                    elif c.getIdentityType() == 'chk':
                        #content = 'chk content'
                        if a.getState(c.getIdentity()):
                            content = c.getReportContentWhenSelected()
                        else:
                            content = c.getReportContentWhenUnselected()
                    else: # i.e. for Textbox, Numeric Box
                        content = '"' + str(a.getState(c.getIdentity())).replace('\r\n',',') + '"'

                #### get the "Option Chosen" if appropriate for question type
                if c.getIdentityType()=='sel':
                    option = []
                    index = 0
                    for d in c.getChildren():
                        if a.getState(d.getIdentity()):
                            if index == 0:
                                option.append('"' + d.getId())
                            else:
                                option.append(d.getId())
                            index += 1
                    if len(option) == 0:
                        option = ['']
                    else:
                        length = len(option)-1
                        option[length] = option[length] + '"'
                elif c.getIdentityType() in ['text','num']:
                    option = ''
                elif c.getIdentityType() == 'chk':
                    if c.getDisplayFormat() != 'CheckBox':
                        if a.getState(c.getIdentity()):
                            option = string.split(c.getDisplayFormat(),'/')[0]
                        else:
                            option = string.split(c.getDisplayFormat(),'/')[1]
                    else: # Displayformat == 'CheckBox'
                        if a.getState(c.getIdentity()):
                            option = 'Selected'
                        else:
                            option = 'Unselected'
                else: # that pretty much just leaves MCQs
                    option = '"' + str(a.getState(c.getIdentity())).replace('\r\n',',') + '"'

                #### combine content, option and score (if necessary) and return the completed row
                if data == "All":

                    if c.isPublishable() and c.isInstanceOf("Scorable"):
                        if c.showScore:
                            row.append(a.getScore(c.getIdentity()))
                        else:
                            row.append('')
                        if c.getIdentityType()=='sel':
                            row.extend(option) # option is a list
                            row.extend(content) # content is a list
                        else:
                            row.append(option) # option is a string
                            row.append(content) # content is a string
                    else:
                        row.append('')  # blank because not scored
                        if c.getIdentityType()=='sel':
                            row.extend(option) # option is a list
                            row.extend(content) # content is a list
                        else:
                            row.append(option) # option is a string                
                            row.append(content) # content is a string

                else:  # data == "Content" (we handled data == 'Scores' already)

                    if c.getIdentityType()=='sel':
                        row.extend(option) # option is a list
                        row.extend(content) # content is a list
                    else:
                        row.append(option) # option is a string                
                        row.append(content) # content is a string  

            return row
        
    #---------------------------------------------------------------#
    
    def csvScoredDump(self, assessments, scored, data):
        """
            Returns a array of data of all the scored components.
            Sub sections are also iterated over.

        """

        array=[]
    
        for c in self.getChildren():
            if c.meta_type=='Section':
                array.extend(c.csvScoredDump(assessments, data))
            else:
                if c.isPublishable() and c.isInstanceOf("Scorable"):
                    if c.showScore:
                        if data == 'Scores':
                            row = self.getDumpData(c, assessments, data)
                        else: # data == "All" or "Content"
                            row = self.getDumpData(c, assessments, data)
                        array.append(row)
                        
        return array

    #---------------------------------------------------------------#

    def csvNonScoredDump(self, assessments, scored, data):
        """
            Returns a array of data of all the non-scored components.
            Sub sections are also iterated over.
            
        """

        array=[]
        include = []
        
        for c in self.getChildren():
            if c.meta_type=='Section':
                array.extend(c.csvNonScoredDump(assessments, data))
            else:
                # include non-scorable components
                if not c.meta_type=="Paragraph" and not c.isInstanceOf("Scorable"):
                    include.append(c)
                # also include scorable components that are not to be displayed
                if hasattr(c, 'showScore') and not c.showScore:
                    include.append(c)
        for c in include:
            if data == 'Scores':
                row = []
            else: # data == "All" or "Content"
                row = self.getDumpData(c, assessments, data)
            array.append(row)
            
        return array
        
    #---------------------------------------------------------------#

    def csvAllDump(self, assessments, scored, data):
        """
            Returns a array of scores/options/report data of all the components.
            Sub sections are also iterated over.
        """

        array=[]
    
        for c in self.getChildren():
            if c.meta_type=='Section':
                array.extend(c.csvAllDump(assessments, scored, data))
            else:
                if c.isPublishable() and c.isInstanceOf("Scorable"):
                    row = self.getDumpData(c, assessments, data)
                    array.append(row)
                elif c.meta_type != "Paragraph":
                    if data == 'Scores':
                        row = []
                    else: # data == "All" or "Content"
                        row = self.getDumpData(c, assessments, data)
                    if row != []:
                        array.append(row)
        return array

    #---------------------------------------------------------------#
    
    def getNavigationTitle(self):
        """
            Return the navigation title for this component.
            This text is used to navigate to this component.
            eg: This text is used in the dropdown lists of the toolbars
        """
        
        return self.navigationTitle
    
    #---------------------------------------------------------------#
        
#####################################################################
## Class Initialisation

# Register Component Information with the Knowledge Engine
registerGenericObjectClass(Section)

#####################################################################
